#define CONNECTIONS_CAPACITY	5
#define THREADS_CAPACITY		3

enum enum_threads {
	SEND_DISCOVERY_MESSAGE = 0,
	RECEIVE_DISCOVERY_MESSAGE,
	ACCEPT_CONNECTION
};

gboolean started = FALSE;
int svr_tcp_sck = 0, svr_udp_sck = 0;
int svr_tcp_size = 0, svr_udp_size = 0;
pthread_t threads[THREADS_CAPACITY];
struct sockaddr_in svr_tcp_addr, svr_udp_addr;
void close_chat(GtkButton *, Chat *);
void open_chat(Chat *);
void update_status_bar(char *);
void update_tree_view( );

/**
** @fn print_error
** @brief prints an error and exits the program
*/

void print_error( ) {
	perror("ERROR: ");
	exit(EXIT_FAILURE);
}

/**
** @fn connect_server
** @brief connects client to server
** @param chat is a pointer of Chat
*/

int connect_server(Chat *chat) {
	chat->socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	return connect(chat->socket, (struct sockaddr *) &chat->sck_addr, chat->size);
}

/**
** @fn send_discovery_request_message
** @brief interface function to send a discovery request message
*/

void send_discovery_request_message( ) {
	IFProtMessage msg;
	struct sockaddr_in bcast;
	int bcast_value = 1;
	if(setsockopt(svr_udp_sck, SOL_SOCKET, SO_BROADCAST, &bcast_value, sizeof(bcast_value)) != ERROR) {
		memset((struct sockaddr*) &bcast, 0, sizeof(bcast));
		bcast.sin_family = PF_INET;
		bcast.sin_port = htons(PORT_STD);
		bcast.sin_addr.s_addr = htonl(INADDR_BROADCAST);
		create_discovery_request(&msg);
		if(sendto(svr_udp_sck, &msg, sizeof msg, 0, (struct sockaddr *) &bcast, sizeof(bcast)) == ERROR)
			update_status_bar(SEND_ERROR);
	}
	else update_status_bar(BROADCAST_ERROR);
}

/**
** @fn send_discovery_request_messages
** @brief sends discovery request messages every timeout
*/

void * send_discovery_request_messages( ) {
	while(TRUE) {
		send_discovery_request_message( );
		sleep(TIMEOUT);
	}
}

/**
** @fn receive_discovery_messages
** @brief receives discovery messages
** @param message is a pointer of IFProtMessage
** @param chat is a pointer of Chat
*/

void receive_discovery_messages(IFProtMessage *message, struct sockaddr_in *sck_addr) {
	Chat * ch = new_chat(message->data, sck_addr);
	ch->sck_addr.sin_port = htons(message->port_src);
	GSList *found = g_slist_find_custom(chats, ch, (GCompareFunc) compare_address);
	if(found != NULL) {
		strcpy(((Chat *) found->data)->username, ch->username);
		((Chat *) found->data)->sck_addr.sin_port = ch->sck_addr.sin_port;
		free(ch);
	}
	else {
		gchar addr[INET_ADDRSTRLEN], command[256]; 
		inet_ntop(AF_INET, &(ch->sck_addr.sin_addr), addr, INET_ADDRSTRLEN);
		sprintf(command, "ifconfig | grep %s >> /dev/null", addr);
		if(system(command) == 256)
			chats = g_slist_prepend(chats, ch);
	}
	update_tree_view( );
}

/**
** @fn receive_udp_messages
** @brief receives udp messages
*/

void * receive_udp_messages( ) {
	while(TRUE) {
		IFProtMessage msg;
		struct sockaddr_in rmt;
		int size = sizeof(rmt);
		memset(&rmt, 0, size);
		if(recvfrom(svr_udp_sck, &msg, sizeof(msg), 0, (struct sockaddr*) &rmt, (socklen_t*) &size) != ERROR) {
			switch(msg.type) {
				case DISCOVERY_REQUEST:
				case DISCOVERY_RESPONSE: receive_discovery_messages(&msg, &rmt); break;
			}
		}
		else update_status_bar(RECEIVE_ERROR);
	}
}

/**
** @fn init_udp_server
** @brief initializes the UDP server
*/

void init_udp_server( ) {
	svr_udp_sck = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	svr_udp_size = sizeof svr_udp_addr;
	memset(&svr_udp_addr, 0, svr_udp_size);
	svr_udp_addr.sin_family = PF_INET;
	svr_udp_addr.sin_port = htons(PORT_STD);
	svr_udp_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	if(bind(svr_udp_sck, (struct sockaddr*) &(svr_udp_addr), svr_udp_size) == ERROR)
		print_error( );
}

/**
** @fn receive_chat_messages
** @brief receives udp messages
*/

void * receive_chat_messages(void *chat) {
	while(TRUE) {
		char str[USERNAME_CAPACITY + 1 + DATA_CAPACITY];
		Chat *ch = (Chat *) chat;
		IFProtMessage msg;
		switch(recv(ch->socket, &msg, sizeof(msg), 0)) {
			case 0:
				pthread_kill(ch->thread, 0);
				close_chat(NULL, ch);
				break;
			case ERROR:
				update_status_bar(RECEIVE_ERROR);
				break;
			default:
				sprintf(str, "%s: %s\n\n", ch->username, msg.data);		
				gtk_text_buffer_insert_at_cursor(gtk_text_view_get_buffer(GTK_TEXT_VIEW(ch->widgets[TEXTVIEW_0])), str, strlen(str));
				sprintf(str, "%d %s/%d %s", ch->counter_posts, POSTS, ++ch->counter_recvs, MESSAGES_RECEIVED);		
				update_status_bar(str);
				break;
		}
	}
}

/**
** @fn accept_connections
** @brief accepts connections
*/

void * accept_connections( ) {
	while(TRUE) {
		Chat * ch = new_chat("", NULL);
		int sck = accept(svr_tcp_sck, (struct sockaddr *) &ch->sck_addr, (socklen_t*) &ch->size);
		if(sck != ERROR) {
			GSList *found = g_slist_find_custom(chats, ch, (GCompareFunc) compare_address);
			if(found != NULL) {
				memset(&((Chat *) found->data)->sck_addr, 0, ((Chat *) found->data)->size);
				((Chat *) found->data)->sck_addr.sin_family = ch->sck_addr.sin_family;
				((Chat *) found->data)->sck_addr.sin_port = ch->sck_addr.sin_port;
				((Chat *) found->data)->sck_addr.sin_addr.s_addr = ch->sck_addr.sin_addr.s_addr;
				((Chat *) found->data)->socket = sck;
				gdk_threads_enter( );				
				open_chat(((Chat *) found->data));
				gdk_threads_leave( );
				pthread_create(&((Chat *) found->data)->thread, NULL, receive_chat_messages, found->data);
			}
			else close(sck);
		}
		else update_status_bar(ACCEPT_ERROR);
		free(ch);
	}
}

/**
** @fn init_tcp_server
** @brief initializes the TCP server
*/

void init_tcp_server( ) {
	svr_tcp_sck = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	svr_tcp_size = sizeof svr_tcp_addr;
	memset(&svr_tcp_addr, 0, svr_tcp_size);
	svr_tcp_addr.sin_family = PF_INET;
	svr_tcp_addr.sin_port = htons(port_usr);
	svr_tcp_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	if(bind(svr_tcp_sck, (struct sockaddr*) &(svr_tcp_addr), svr_tcp_size) != ERROR) {
		if(listen(svr_tcp_sck, CONNECTIONS_CAPACITY) == ERROR)
			print_error( );
	}
	else print_error( );
}

/**
** @fn init_network_services
** @brief interface function to initialize network services
*/

void init_network_services( ) {
	init_udp_server( );
	init_tcp_server( );
}

/**
** @fn start_network_services
** @brief interface function to start network services
*/

void start_network_services( ) {
	pthread_create(&threads[SEND_DISCOVERY_MESSAGE], NULL, send_discovery_request_messages, NULL);
	pthread_create(&threads[RECEIVE_DISCOVERY_MESSAGE], NULL, receive_udp_messages, NULL);
	pthread_create(&threads[ACCEPT_CONNECTION], NULL, accept_connections, NULL);
	started = TRUE;
}

/**
** @fn stop_network_services
** @brief interface function to stop network services
*/

void stop_network_services( ) {
	int i;
	if(started) {
		for(i = 0; i < THREADS_CAPACITY; ++i)
			pthread_kill(threads[i], 0);
	}
	for(i = 0; i < g_slist_length(chats); ++i) {
		Chat *ch = g_slist_nth_data(chats, i);
		close(ch->socket);
	}
}

/**
** @fn end_network_services
** @brief interface function to end network services
*/

void end_network_services( ) {
	stop_network_services( );
	close(svr_tcp_sck);
	close(svr_udp_sck);
}
